package com.google.code.opengw.server.domain;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import com.google.code.opengw.http.HTTPConstants;
import com.google.code.opengw.support.OpenGWException;
import com.google.code.opengw.support.OpenGWLogger;


public class Domain {
	private final String name;
	private final int port;
	private final int initialWorkerCount;
	private final int workerIncreasementCount;
	private final int maxWorkerCount;
	private final int connectionQueueSize;
	private final WorkerManager workerManager;
	private final ConnectionQueue connectionQueue;
	private final WorkerPool workerPool;
	private final HTTPListener httpListener;
	private HashMap<String, Route> routeTable;
	private HashMap<String, Publish> publishTable;
	
	public Domain(String name, int port, int initialWorkerCount,
			int workerIncreasementCount, int maxWorkerCount, int connectionQueueSize){
		this.name = name;
		this.port = port;
		this.initialWorkerCount = initialWorkerCount;
		this.workerIncreasementCount = workerIncreasementCount;
		this.maxWorkerCount = maxWorkerCount;
		this.connectionQueueSize = connectionQueueSize;
		connectionQueue = new ConnectionQueue(connectionQueueSize);
		workerPool = new WorkerPool(initialWorkerCount, workerIncreasementCount, maxWorkerCount, connectionQueue);
		routeTable = new HashMap<String, Route>();
		publishTable = new HashMap<String, Publish>();
		workerManager = new WorkerManager(this, workerPool);
		httpListener = new HTTPListener(this, connectionQueue);
		
	}
	
	public void startDomain(){
		OpenGWLogger.addInfo(this.getClass(), "Domain is starting: " + name);
		new Thread(workerManager).start();
		new Thread(httpListener).start();
		OpenGWLogger.addInfo(this.getClass(), "Domain is started: " + name);
	}
	
	public void stopDomain() throws IOException{
		OpenGWLogger.addInfo(this.getClass(), "Domain is closing: " + name);
		workerManager.setSystemActive(false);
		httpListener.setSystemActive(false);
		httpListener.closeServer();
		
		while (httpListener.isListenerAlive() || workerManager.isManagerAlive()) {
			try{
				Thread.sleep(1000);
			} catch (Exception e) {
			}
		}
		OpenGWLogger.addInfo(this.getClass(), "Domain is closed: " + name);
	}
	
	public void addPublish(Publish publish) throws OpenGWException{
		if(publishTable.get(publish.getPublishName()) != null)
			throw new OpenGWException(OpenGWException.format(OpenGWException.PUBLISH_ALREADY_EXIST, publish.getPublishName(), publish.getDomainName()));
		for(Route route : publish.getRouteList()){
			if(routeTable.get(route.getRequestPath()) != null)
				throw new OpenGWException(OpenGWException.format(OpenGWException.REQUEST_PATH_ALREADY_EXIST, route.getRequestPath()));
		}
		for(Route route : publish.getRouteList()){
			routeTable.put(route.getRequestPath(), route);
		}
		publishTable.put(publish.getPublishName(), publish);
		OpenGWLogger.addInfo(this.getClass(), "Publish initialized : " + publish.toString());
	}
	
	public Publish removePublish(String publishName) throws OpenGWException{
		Publish publish = publishTable.remove(publishName);
		if(publish == null)
			throw new OpenGWException(OpenGWException.format(OpenGWException.PUBLISH_NOT_FOUND, publishName));
		for(Route route : publish.getRouteList()){
			routeTable.remove(route.getRequestPath());
		}
		OpenGWLogger.addInfo(this.getClass(), "Publish and routes removed from datastore : " + publish.getPublishName());
		
		return publish;
	}
	
	public Publish getPublish(String publishName) throws OpenGWException{
		Publish publish = publishTable.get(publishName);
		if(publish == null)
			throw new OpenGWException(OpenGWException.format(OpenGWException.PUBLISH_NOT_FOUND, publishName));
		return publish;
	}
	
	public boolean isPublishNameExists(String publishName){
		return publishTable.containsKey(publishName);
	}
	
	public List<String> getPublishNameList(){
		
		List<String> publishDirectoryList = new ArrayList<String>();
		Set<String> keySet = publishTable.keySet();
		for(String key : keySet){
			publishDirectoryList.add(key);
		}
		
		return publishDirectoryList;
	}
	
	public Route getRoute(String requestPath) throws OpenGWException{
				
		Route route = null;
		int separatorInd = requestPath.lastIndexOf('/');
		route = routeTable.get(requestPath);
		if(route == null && requestPath.endsWith("/")){
			requestPath = requestPath.substring(0, separatorInd);
			route = routeTable.get(requestPath);
			separatorInd = requestPath.lastIndexOf('/');
		} 
		
		while(route == null && separatorInd > 0){
			requestPath = requestPath.substring(0, separatorInd + 1);
			route = routeTable.get(requestPath);
			if(route == null){
				requestPath = requestPath.substring(0, separatorInd);
				route = routeTable.get(requestPath);
				separatorInd = requestPath.lastIndexOf('/');
			}
		}
		if(route == null){
			requestPath = "/";
			route = routeTable.get(requestPath);
		}
		
		if(route == null)
			throw new OpenGWException(OpenGWException.NOT_FOUND_REQUEST_URL, HTTPConstants.CODE_404, requestPath);
		return route;
	}

	public String getName() {
		return name;
	}

	public int getPort() {
		return port;
	}

	public int getInitialWorkerCount() {
		return initialWorkerCount;
	}

	public int getWorkerIncreasementCount() {
		return workerIncreasementCount;
	}

	public int getMaxWorkerCount() {
		return maxWorkerCount;
	}

	public int getConnectionQueueSize() {
		return connectionQueueSize;
	}

	@Override
	public String toString() {
		return "Domain [name=" + name + ", port=" + port
				+ ", initialWorkerCount=" + initialWorkerCount
				+ ", workerIncreasementCount=" + workerIncreasementCount
				+ ", maxWorkerCount=" + maxWorkerCount + ", connectionQueueSize="
				+ connectionQueueSize + "]";
	}
}
